package com.ndp.fb.log;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.recovery.ResilientFileOutputStream;
import ch.qos.logback.core.rolling.RollingPolicy;
import ch.qos.logback.core.rolling.TriggeringPolicy;
import ch.qos.logback.core.rolling.helper.RenameUtil;
import ch.qos.logback.core.spi.LogbackLock;
import ch.qos.logback.core.util.FileUtil;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by yeahmobi on 2015/3/24.r
 */
public class FbLogFileAppender<E> extends UnsynchronizedAppenderBase<E> {
    private LogbackLock lock = new LogbackLock();
    private String fileSeparator = System.getProperty("file.separator");
    private String FILE_KEY = "file";
    private String OUTPUT_KEY = "output";
    private String LOG_SUFFIX = ".log";
    private String DEFAULT_FILE_NAME = "normal";


    private Encoder<E> encoder;
    private TriggeringPolicy<E> triggeringPolicy;
    private RollingPolicy rollingPolicy;
    private String logDir;
    private String fileSuffix;
    private int maxIndex = 20;

    private Map<String, Map<String, Object>> logFileMap = new HashMap();
    private RenameUtil util = new RenameUtil();

    @Override
    protected void append(E event) {
        try {
            if (!isStarted()) {
                addError("xxxxxxxxxxxx is not start !!!");
                return;
            }
            ILoggingEvent loggingEvent = (ILoggingEvent)event;
            String loggerName = loggingEvent.getLoggerName();
            if(!loggerName.startsWith("com.ndp")){
                return;
            }
            String logFileName = getLogFileName(logDir, loggerName);
            synchronized (logFileMap) {
                if (!logFileMap.containsKey(logFileName)) {
                    openFile(logFileName);
                }
            }
            synchronized (lock) {
                Map<String, Object> logMap = logFileMap.get(logFileName);
                File logFile = (File) logMap.get(FILE_KEY);
                if (triggeringPolicy.isTriggeringEvent(logFile, event)) {
                    OutputStream out = (OutputStream) logMap.get(OUTPUT_KEY);
                    out.close();

                    File file = new File(logFileName + "." + maxIndex);
                    if (file.exists()) {
                        file.delete();
                    }
                    for (int i = maxIndex - 1; i >= 0; i--) {
                        String toRenameStr;
                        if (i == 0) {
                            toRenameStr = logFileName;
                        } else {
                            toRenameStr = logFileName + "." + i;
                        }

                        File toRename = new File(toRenameStr);
                        if (toRename.exists()) {
                            util.rename(toRenameStr, logFileName + "." + (i + 1));
                        } else {
                            addInfo("Skipping roll-over for inexistent file " + toRenameStr);
                        }
                    }
                    openFile(logFileName);
                    logMap = logFileMap.get(logFileName);
                }
                OutputStream outputStream = (OutputStream) logMap.get(OUTPUT_KEY);
                writeOut(outputStream, event);
            }
        } catch (Exception e) {
            e.printStackTrace();
            addError("append error  failed.", e);
        }
    }

    private void writeOut(OutputStream outputStream, E event) throws IOException {
        encoder.init(outputStream);
        this.encoder.doEncode(event);
    }


    private void openFile(String logFileName) throws IOException {
        Map<String, Object> logMap = new HashMap();
        File file = new File(logFileName);
        if (FileUtil.isParentDirectoryCreationRequired(file)) {
            boolean result = FileUtil.createMissingParentDirectories(file);
            if (!result) {
                addError("Failed to create parent directories for [" + file.getAbsolutePath() + "]");
            }
        }
        ResilientFileOutputStream resilientFos = null;
        resilientFos = new ResilientFileOutputStream(file, true);
        resilientFos.setContext(context);
        logMap.put(FILE_KEY, file);
        logMap.put(OUTPUT_KEY, resilientFos);
        logFileMap.put(logFileName, logMap);
    }

    private String getLogFileName(String currentlyLogDir, String loggerName) {
        String logFileName;
        String[] split = loggerName.split("\\.");
        if (split.length >= 5) {
            logFileName = split[3] + "_" + split[4];
        } else if (split.length >= 4) {
            logFileName = split[3];
        } else {
            logFileName = DEFAULT_FILE_NAME;
        }

        logFileName = currentlyLogDir + fileSeparator + logFileName;
        if (fileSuffix != null && !"".equals(fileSuffix)) {
            logFileName = logFileName + "_" + fileSuffix;
        }
        return logFileName + LOG_SUFFIX;
    }

    public void setEncoder(Encoder<E> encoder) {
        this.encoder = encoder;
    }

    public void setLogDir(String logDir) {
        this.logDir = logDir;
    }

    public int getMaxIndex() {
        return maxIndex;
    }

    public void setMaxIndex(int maxIndex) {
        this.maxIndex = maxIndex;
    }

    public RollingPolicy getRollingPolicy() {
        return rollingPolicy;
    }

    public TriggeringPolicy<E> getTriggeringPolicy() {
        return triggeringPolicy;
    }

    public String getFileSuffix() {
        return fileSuffix;
    }

    public void setFileSuffix(String fileSuffix) {
        this.fileSuffix = fileSuffix;
    }

    /**
     * Sets the rolling policy. In case the 'policy' argument also implements
     * {@link TriggeringPolicy}, then the triggering policy for this appender is
     * automatically set to be the policy argument.
     *
     * @param policy
     */
    @SuppressWarnings("unchecked")
    public void setRollingPolicy(RollingPolicy policy) {
        rollingPolicy = policy;
        if (rollingPolicy instanceof TriggeringPolicy) {
            triggeringPolicy = (TriggeringPolicy<E>) policy;
        }

    }

    public void setTriggeringPolicy(TriggeringPolicy<E> policy) {
        triggeringPolicy = policy;
        if (policy instanceof RollingPolicy) {
            rollingPolicy = (RollingPolicy) policy;
        }
    }
}
